package com.yanyu.space.file.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yanyu.space.common.core.exception.ServiceException;
import com.yanyu.space.file.bean.bo.*;
import com.yanyu.space.file.bean.enums.FileRCode;
import com.yanyu.space.file.bean.enums.FileUploadTypeEnum;
import com.yanyu.space.file.bean.po.FileInfo;
import com.yanyu.space.file.dao.FileInfoMapper;
import com.yanyu.space.file.dao.FileInfoShardingMapper;
import com.yanyu.space.file.service.IFileInfoService;
import com.yanyu.space.file.util.MinioHelper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.List;

/**
 * 功能: 文件 业务实现类
 *
 * @author yanyu
 * @date 2022-11-15
 */
@Service
@Slf4j
@AllArgsConstructor
public class FileInfoServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> implements IFileInfoService {

    private MinioHelper minioHelper;
    private FileInfoShardingMapper fileInfoShardingMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FileInfoBo> directUpload(FileDirectSearchBo bo) {
        List<FileInfoBo> fileInfoBos = new ArrayList<>(bo.getFiles().length);
        try {
            for (MultipartFile file : bo.getFiles()) {
                // 1. 查询文件是否存在，存在则不在上传
                String fileMd5 = MD5.create().digestHex(file.getBytes());
                FileInfo tmpFileInfo = this.baseMapper.selectOne(Wrappers.<FileInfo>lambdaQuery().eq(FileInfo::getFileMd5, fileMd5));
                if (tmpFileInfo != null) {
                    fileInfoBos.add(BeanUtil.copyProperties(tmpFileInfo, FileInfoBo.class));
                    continue;
                }

                // 2. 上传文件
                String fileSuffix = StringUtils.substringAfterLast(file.getOriginalFilename(), ".");
                String fileName = StringUtils.join(fileMd5, ".", fileSuffix);
                String minioUrl = minioHelper.upload(file, fileName);

                // 3. 新增或更新存储信息
                if (tmpFileInfo == null) {
                    tmpFileInfo = new FileInfo();
                }
                tmpFileInfo.setFileName(file.getOriginalFilename());
                tmpFileInfo.setFileSize(file.getSize());
                tmpFileInfo.setFileType(file.getContentType());
                tmpFileInfo.setFileMd5(fileMd5);
                tmpFileInfo.setFileSuffix(fileSuffix);
                tmpFileInfo.setFileUrl(minioUrl);
                tmpFileInfo.setUploadType(FileUploadTypeEnum.DIRECT.getValue());
                if (tmpFileInfo.getId() != null) {
                    this.baseMapper.updateById(tmpFileInfo);
                } else {
                    this.baseMapper.insert(tmpFileInfo);
                }
                fileInfoBos.add(BeanUtil.copyProperties(tmpFileInfo, FileInfoBo.class));
            }
        } catch (Exception e) {
            log.error("上传文件失败 -> {}", e.getMessage(), e);
            throw new ServiceException(FileRCode.FILE_UPLOAD_FAIL);
        }
        return fileInfoBos;
    }

    @Override
    public FileShardingInfoBo shardingUpload(FileShardingSearchBo bo) {
        // 文件是否存在
        FileInfo fileInfo = this.baseMapper.selectOne(Wrappers.<FileInfo>lambdaQuery().eq(FileInfo::getFileMd5, bo.getFileMd5()));
        if (fileInfo != null) {
            log.info("文件已存在，fileName -> {}, fileMd5-> {}", bo.getOriginalFileName(), bo.getFileMd5());
            throw new ServiceException(FileRCode.FILE_EXSIT);
        }

        // 计算分片数量
        double totalChunk = Math.ceil(new Double(bo.getFileSize()) / bo.getChunkSize());
        String fileSuffix = StringUtils.substringAfterLast(bo.getOriginalFileName(), ".");
        String fileName = StringUtils.join(bo.getFileMd5(), ".", fileSuffix);
        FileShardingInfoBo fileShardingInfoBo = minioHelper.shardingUpload(fileName, (int)totalChunk);
        if (fileShardingInfoBo == null) {
            throw new ServiceException();
        }

        // 插入数据
        fileInfo = new FileInfo();
        fileInfo.setOriginalFileName(bo.getOriginalFileName());
        fileInfo.setFileName(fileName);
        fileInfo.setFileSize(bo.getFileSize());
        fileInfo.setFileType(fileSuffix);
        fileInfo.setFileMd5(bo.getFileMd5());
        fileInfo.setFileSuffix(fileSuffix);
        fileInfo.setUploadType(FileUploadTypeEnum.SHARDING.getValue());
        this.baseMapper.insert(fileInfo);
        return fileShardingInfoBo;
    }

    @Override
    public FileInfoBo shardingMergeUpload(FileShardingMergeSearchBo bo) {
        // 1. 基础信息判断
        FileInfo fileInfo = this.baseMapper.selectOne(Wrappers.<FileInfo>lambdaQuery().eq(FileInfo::getFileMd5, bo.getFileMd5()));
        if (fileInfo == null) {
            log.info("文件不存在，fileMd5-> {}", bo.getFileMd5());
            throw new ServiceException(FileRCode.FILE_NOT_EXSIT);
        }
        if (!FileUploadTypeEnum.SHARDING.match(fileInfo.getUploadType())) {
            log.info("文件不是分片 -> {}", JSONObject.toJSONString(fileInfo));
            throw new ServiceException(FileRCode.FILE_MERGE_FAIL);
        }

        try {
            // 2. 合并文件分片
            String result = minioHelper.mergeShardingUpload(bo.getFileName(), bo.getUploadId());

            // 3. 更新文件地址和文件分片状态
            if (StringUtils.isNoneBlank(result)) {
                fileInfo.setFileUrl(result);
                baseMapper.update(fileInfo, Wrappers.<FileInfo>lambdaUpdate().eq(FileInfo::getFileMd5, bo.getFileMd5()));
                return BeanUtil.copyProperties(fileInfo, FileInfoBo.class);
            } else {
                // 4. 合并失败，更新文件分片状态
                log.info("合并文件分片失败");
                throw new ServiceException(FileRCode.FILE_MERGE_FAIL);
            }
        } catch (Exception e) {
            // 4. 合并失败，更新文件分片状态
            throw new ServiceException(FileRCode.FILE_MERGE_FAIL, e);
        }
    }
}
